#!/usr/bin/env php
<?php declare (ticks = 1);
class App {
	const NAME = 'antlion';
	const VERSION = '0.%rev%';
	const MKSEC_SLEEP = 100000;
	protected static $JSON_ERRORS = array (
		JSON_ERROR_NONE      => 'no errors',
		JSON_ERROR_DEPTH     => 'stack overflow',
		JSON_ERROR_CTRL_CHAR => 'control character error, possibly incorrectly encoded',
		JSON_ERROR_SYNTAX    => 'syntax error',
	);
	protected $Sockets = array ();
	protected $Config = FILE_CONFIG;
	protected $QCrash = FALSE;
	protected $QConfig = FALSE;

	public function __construct () {
		// *** PARSE OPTIONS ***
		$Opts ['c'] = new CommandLineOption         (FILE_CONFIG, 'config file name');
		$Opts ['d'] = new CommandLineSwitch         ('(overrides -l) set debug level to maximum');
		$Opts ['g'] = new CommandLineOption         ('', 'group to change to');
		$Opts ['h'] = new CommandLineSwitch         ('print this short help and exit');
		$Opts ['l'] = new CommandLineOptionLogLevel (LOG_WARNING, 'syslog level (integer or string)');
		$Opts ['n'] = new CommandLineSwitch         ('do not fork, stay in foreground, log to stderr');
		$Opts ['p'] = new CommandLineOption         (FILE_PID, 'pidfile name');
		$Opts ['u'] = new CommandLineOption         ('', 'user to change to');
		$Opts ['v'] = new CommandLineSwitch         ('show version and exit');
		// Construct getopt's argument
		$Temp = '';
		foreach ($Opts as $Letter => $Opt) {
			$Temp .= $Letter . $Opt->GetOpt;
		}
		// Parse the options from command line
		if (FALSE === ($Temp = getopt ($Temp))) {
			die (M__APP_ERR_GETOPT."\n");
		}
		try {
			foreach ($Temp as $Letter => $Value) {
				$Opts [$Letter]->Set ($Value);
			}
		} catch (X_CommandLineOptionLogLevel $X) {
			die (sprintf (M__CLO_ERR_SYSLOG_LEVEL."\n", $X->getMessage ()));
		}
		// Override -l with -d
		if ($Opts ['d']->Value) {
			$Opts ['l']->Set (LOG_DEBUG);
		}
		// Special modes (help and version)
		if ($Opts ['v']->Value) {
			printf (M__APP_VERSION."\n", self::NAME, self::VERSION);
			exit (0);
		}
		if ($Opts ['h']->Value) {
			printf (M__APP_USAGE_HEADER."\n", basename ($GLOBALS ['argv'][0]));
			foreach ($Opts as $Letter => $Opt) {
				printf (M__APP_USAGE_LINE."\n", $Letter, $Opt->Usage);
			}
			exit (0);
		}
		// Store config name
		$this->Config = $Opts ['c']->Value;
		// *** SUMMON A DAEMON ***
		Daemon::Summon ($Opts ['l']->Value, $Opts ['u']->Value, $Opts ['g']->Value);
		if (! $Opts ['n']->Value) {
			Daemon::Detach ();
		}
		// debug options table
		Daemon::Debug (M__APP_OPTIONS_DEBUG_HEADER);
		foreach ($Opts as $Letter => $Opt) {
			$Explicit = ($Opt->Explicit) ? '*' : ' ';
			if (is_bool ($Value = $Opt->Value)) {
				$Value = ($Value) ? 'yes' : 'no';
			} elseif (strlen ($Value)) {
				$Value = "\"$Value\"";
			} else {
				$Value = 'empty';
			}
			Daemon::Debug (M__APP_OPTIONS_DEBUG_LINE, $Letter, $Explicit, $Value);
		}
		// *** INSTALL SIGNAL HANDLERS ***
		foreach (array (SIGINT, SIGQUIT, SIGTERM) as $Sig) {
			pcntl_signal ($Sig, array ($this, 'sig_crash'));
		}
		pcntl_signal (SIGHUP, array ($this, 'sig_conf'));
	}
	protected function sig_crash ($Sig) {
		Daemon::Info (M__APP_SIG_CRASH, $Sig);
		$this->QCrash = TRUE;
	}
	protected function sig_conf ($Sig) {
		Daemon::Info (M__APP_SIG_CONF, $Sig);
		$this->QConfig = TRUE;
	}
	protected function Configure () {
		Daemon::Info (M__APP_CONF_INIT);
		$JSON = @file_get_contents ($this->Config);
		if (FALSE === $JSON) {
			Daemon::Warn (M__APP_CONF_ERR_READ, $this->Config);
			return (FALSE);
		}
		$JSON = json_decode ($JSON, TRUE);
		if (NULL === $JSON) {
			$Error = json_last_error ();
			Daemon::Warn (M__APP_CONF_ERR_JSON, $this->Config, self::$JSON_ERRORS [$Error], $Error);
			return (FALSE);
		}
		Daemon::Debug (M__APP_CONF_DEBUG_JSON);
		Daemon::Debug (print_r ($JSON, TRUE));

		// (re)create sockets
		if (array_key_exists ('Sockets', $JSON)) {
			if (! is_array ($JSON ['Sockets'])) {
				$JSON ['Sockets'] = array ($JSON ['Sockets']);
			}
			// close all opened sockets
			foreach ($this->Sockets as $Socket) {
				$Socket->Close ();
			}
			$this->Sockets = array ();
			// Extract single ports into address pairs
			$Sockets = array ();
			foreach ($JSON ['Sockets'] as $Address) {
				if (is_integer ($Address)) {
					array_push ($Sockets, ':::'.$Address);
					array_push ($Sockets, '0.0.0.0:'.$Address);
				} else {
					array_push ($Sockets, $Address);
				}
			}
			// Now try to create them
			foreach ($Sockets as $Address) {
				try {
					Daemon::Debug (M__APP_CONF_SOCKET_CREATE, $Address);
					$Socket = new Socket ($Address);
					array_push ($this->Sockets, $Socket);
				} catch (X_Socket $X) {
					Daemon::Warn (M__APP_CONF_SOCKET_X, $Address, $X->getMessage (), $X->getCode ());
				}
			}
		} else {
			Daemon::Warn (M__APP_CONF_NO_SOCKETS);
		}


// (re)connect to DB
/*
		if (! array_key_exists ('PDO', $JSON) or ! array_key_exists ('DSN', $JSON ['PDO'])) {
			Daemon::Warn (M__APP_CONF_ERR_PDO_NDEF, $this->Config);
			return (FALSE);
		}
		try {
			if (array_key_exists ('Options', $JSON ['PDO']) and array_key_exists ('User', $JSON ['PDO']) and array_key_exists ('Password', $JSON ['PDO'])) {
				$this->PDO = new PDO ($JSON ['PDO']['DSN'], $JSON ['PDO']['User'], $JSON ['PDO']['Password'], $JSON ['PDO']['Options']);
			} elseif (array_key_exists ('User', $JSON ['PDO']) and array_key_exists ('Password', $JSON ['PDO'])) {
				$this->PDO = new PDO ($JSON ['PDO']['DSN'], $JSON ['PDO']['User'], $JSON ['PDO']['Password']);
			} else {
				$this->PDO = new PDO ($JSON ['PDO']['DSN']);
			}
		} catch (PDOException $X) {
var_dump ($X);
var_dump ($this->PDO);
die ();
		}
*/



		return (TRUE);
	}
	public function TakeOff () {
		if (! $this->Configure ()) {
			Daemon::Error (M__APP_CONF_ERR_FIRST, $this->Config);
		}
		Daemon::Info (M__APP_AIRBORNE);
		while (TRUE) {
			// if there are new connections on sockets, extract them into Connections
			foreach ($this->Sockets as $Socket) {
				$Socket->Accept ();
			}
			$Answers = array ();
			$Queries = Connections::Receive ();
foreach ($Queries as $SID => $Query) {
	$Answers [$SID] = $Query;
}
			if (count ($Answers)) {
				Connections::Transmit ($Answers);
			}
			if ($this->QCrash) {
				Daemon::Info (M__APP_CRASHING);
				// close all opened sockets
				foreach ($this->Sockets as $Socket) {
					$Socket->Close ();
				}
				Connections::Down ();
				Daemon::Banish ();
				exit (0);
			}
			if ($this->QConfig) {
				$this->Configure ();
				$this->QConfig = FALSE;
			}
			usleep (self::MKSEC_SLEEP);
		}
	}
}
	// *** ENTRY POINT ***
	function __autoload ($Class) {
		include_once (DIR_CLASSES."/$Class.php");
	}
	ini_set ('display_errors', 1);
	ini_set ('log_errors', 0);
	error_reporting (E_ALL | E_STRICT);
	define ('DIR_CLASSES', 'classes');
	define ('DIR_MESSAGES', 'messages');
	define ('FILE_CONFIG', 'antlion.conf.js');
	define ('FILE_PID', '/var/run/overwatch/antlion.pid');
	setlocale (LC_ALL, 'C');
	require (DIR_MESSAGES.'/C.php'); // i18n may be implemented here
	$App = new App ();
	$App->TakeOff ();
?>